package com.cpic.nonauto.bloomfilter.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁
 *
 * @author admire
 */
@Component("MyRedisLockHelper")
public class RedisLockHelper {

    public static Logger LOGGER = LoggerFactory.getLogger(RedisLock.class);
    /**
     *
     */
    public static final long DEFAULT_LOCK_EXPIRE_MILLIS = 30 * 1000;
    /**
     *
     */
    public static final long DEFAULT_LOCK_WAIT_MILLIS = 5 * 1000;
    /**
     *
     */
    public static final long DEFAULT_LOCK_SLEEP_MILLIS = 50;


    @Autowired
    private RedisTemplate redisTemplate;


    private GenericToStringSerializer valueSerializer = new GenericToStringSerializer(Object.class);


    public String uniqueId() {
        return UUID.randomUUID() + ":" + Thread.currentThread().getId();
    }

    /**
     * 自定义获取锁超时时间
     *
     * @param key   String
     * @param value String
     * @return
     */

    public boolean lockWithWaitTime(String key, String value) throws InterruptedException {
        return lockWithWaitTime(key, value, DEFAULT_LOCK_EXPIRE_MILLIS, DEFAULT_LOCK_WAIT_MILLIS, DEFAULT_LOCK_SLEEP_MILLIS);
    }

    public boolean lockWithWaitTime(String key, String value, long lockTimeoutMillis, long waitTimeMillis, long retrySleepTimeMillis) throws InterruptedException {

        while (waitTimeMillis >= 0) {
            long current = System.currentTimeMillis();
            boolean result = lockWithLua(key, value, lockTimeoutMillis, TimeUnit.MILLISECONDS);
            if (result) {
                return true;
            }
            //减去加锁时间
            waitTimeMillis -= System.currentTimeMillis() - current;
            if (waitTimeMillis < 0) {
                //等待时间用完直接返回加锁失败
                return false;
            }
            //没有获取到锁等一段时间再试
            waitTimeMillis -= retrySleepTimeMillis;
            Thread.sleep(retrySleepTimeMillis);
        }
        return false;
    }

    /**
     * 直接使用setnx + expire方式获取分布式锁
     * 非原子性
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public boolean lockSetnx(String key, String value, int timeout) {
        Boolean setNxResult = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (null != setNxResult && setNxResult) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 使用Lua脚本，脚本中使用setnx+expire命令进行加锁操作
     *
     * @param key            锁的key
     * @param uniqueId       锁的唯一值，后期用来解锁
     * @param expireTime     锁过期时间
     * @param expireTimeUnit 锁过期时间单位
     * @return
     */
    public boolean lockWithLua(String key, String uniqueId, long expireTime, TimeUnit expireTimeUnit) {
        final String luaScriptsForLock = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
                "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<>(luaScriptsForLock, Long.class);
        long expireMillis = TimeoutUtils.toMillis(expireTime, expireTimeUnit);
        long result = (long) redisTemplate.execute(script, valueSerializer, valueSerializer, Collections.singletonList(key), uniqueId, expireMillis);
        //判断是否成功
        return Objects.equals(result, 1L);
    }

    /**
     * 不校验直接删除，该key可能正在使用
     *
     * @param key
     */

    public void unlockWithNoCheck(String key) {
        redisTemplate.delete(key);
    }


    /**
     * 使用lua脚本进行解锁操作
     *
     * @param key
     * @param value
     * @return
     */
    public boolean unlockWithLua(String key, String value) {
        final String luaScriptsForUnlock = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                "return redis.call('del',KEYS[1]) else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<>(luaScriptsForUnlock, Long.class);
        long result = (long) redisTemplate.execute(script, valueSerializer, valueSerializer, Collections.singletonList(key), Collections.singletonList(value));
        //判断是否成功
        return Objects.equals(result, 1L);
    }


}
